/** @file
  Copyright (C) 2021, Mike Beaton. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-3-Clause
**/

#ifndef LINUX_BOOT_INTERNAL_H
#define LINUX_BOOT_INTERNAL_H

#if !defined(OC_TRACE_GRUB_VARS)
#define OC_TRACE_GRUB_VARS DEBUG_VERBOSE
#endif

#include <Uefi.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcMiscLib.h>

#define IS_DIGIT(c)         ((c) >= '0' && (c) <= '9')
#define IS_ALPHA(c)         (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))

/*
  Allow scan of ESP.
*/
#define LINUX_BOOT_SCAN_ESP             BIT0
/*
  Allow scan of XBOOTLDR.
*/
#define LINUX_BOOT_SCAN_XBOOTLDR        BIT1
/*
  Allow scan of Linux Root filesystems.
*/
#define LINUX_BOOT_SCAN_LINUX_ROOT      BIT2
/*
  Allow scan of Linux Data filesystems.
*/
#define LINUX_BOOT_SCAN_LINUX_DATA      BIT3
/*
  Some space for additional file systems.
*/
/*
  Allow scan of any filesystem not explicitly mentioned
  (including but not limited FAT other than ESP, and NTFS).
*/
#define LINUX_BOOT_SCAN_OTHER           BIT7
/*
  Allow autodetect of vmlinuz-{version} and matching init*-{version},
  if scan for usable /loader/entries fails.
*/
#define LINUX_BOOT_ALLOW_AUTODETECT     BIT8
/*
  Define entry id by the first part (to dash) of the filename
  from which it was created. Results in the first matching entry (after sorting)
  always being the default entry, which results in updated Linux becoming the
  new default automatically.
*/
#define LINUX_BOOT_USE_LATEST           BIT9
/*
  If set, add "ro" as initial option to all distros. Can be sepcified per
  FS by using argument partuuidopts:{partuuid}+=ro instead.
*/
#define LINUX_BOOT_ADD_RO               BIT10
/*
  TODO: Both blspec-style and autodetect can make use of grub.cfg info if this flag is set.
*/
//#define LINUX_BOOT_ALLOW_PARSE_GRUB     BIT11
/*
  Add root= option if missing in loader/entries *.conf options.
*/
#define LINUX_BOOT_ALLOW_CONF_AUTO_ROOT BIT13
/*
  Add some additional log info.
*/
#define LINUX_BOOT_LOG_VERBOSE          BIT14
/*
  Prepend filesystem type and first 8 hex digits of PARTUUID to discovered
  entry titles, to help in debugging where entries came from.
*/
#define LINUX_BOOT_ADD_DEBUG_INFO       BIT15

#define LINUX_BOOT_ALL            ( \
  LINUX_BOOT_SCAN_ESP             | \
  LINUX_BOOT_SCAN_XBOOTLDR        | \
  LINUX_BOOT_SCAN_LINUX_ROOT      | \
  LINUX_BOOT_SCAN_LINUX_DATA      | \
  LINUX_BOOT_SCAN_OTHER           | \
  LINUX_BOOT_ALLOW_AUTODETECT     | \
  LINUX_BOOT_USE_LATEST           | \
  LINUX_BOOT_ADD_RO               | \
  LINUX_BOOT_ALLOW_CONF_AUTO_ROOT | \
  LINUX_BOOT_LOG_VERBOSE          | \
  LINUX_BOOT_ADD_DEBUG_INFO       \
  )

/*
  GRUB var error codes.
*/
#define VAR_ERR_NONE        (0)
#define VAR_ERR_INDENTED    BIT0  // Naive detection of GRUB conditional logic
#define VAR_ERR_HAS_VARS    BIT1  // We do not support nested vars (in name or value), even though GRUB does

/*
  Global flags for this instance of OpenLinuxBoot.efi.
*/
extern UINTN gLinuxBootFlags;

/*
  Boot picker context.
*/
extern OC_PICKER_CONTEXT *gPickerContext;

/*
  Stored parsed load options.
  Would be freed at driver unload, if that happened.
 */
extern OC_FLEX_ARRAY *gParsedLoadOptions;

/*
  The array of loader entries, either really from *.conf files or generated by autodetect.
*/
extern OC_FLEX_ARRAY *gLoaderEntries;

/*
  The current partuuid.
*/
extern EFI_GUID gPartuuid;

/*
  Human readable ascii name of current file system type.
*/
extern CHAR8 *gFileSystemType;

// TODO: Are all of the below types used outside a single file?
// TODO: Is this file sensibly ordered?

/*
  Forward declaration of GRUB_VAR structure.
*/
typedef struct GRUB_VAR_ GRUB_VAR;

/*
  Forward declaration of LOADER_ENTRY structure.
*/
typedef struct LOADER_ENTRY_ LOADER_ENTRY;

/*
  Forward declaration of VMLINUZ_FILE structure.
*/
typedef struct VMLINUZ_FILE_ VMLINUZ_FILE;

/*
  GRUB vars.
*/
EFI_STATUS
InternalInitGrubVars (
  VOID
  );

VOID
InternalFreeGrubVars (
  VOID
  );

EFI_STATUS
InternalSetGrubVar (
  CHAR8 *Key,
  CHAR8 *Value,
  UINTN Errors
  );

BOOLEAN
InternalHasGrubVars (
  CHAR8 *Options
  );

GRUB_VAR *
InternalGetGrubVar (
  IN     CONST CHAR8 *Key
  );

EFI_STATUS
InternalExpandGrubVarsForArray (
  IN OUT       OC_FLEX_ARRAY *Options
  );

EFI_STATUS
InternalExpandGrubVars (
  IN     CONST CHAR8 *Options,
  IN OUT       CHAR8 **Result
  );

/*
  Process grubenv file.
*/
EFI_STATUS
InternalProcessGrubEnv (
  IN OUT       CHAR8              *Content,
  IN     CONST UINTN              Length
  );

/*
  Process grub.cfg file.
*/
EFI_STATUS
InternalProcessGrubCfg (
  IN OUT        CHAR8              *Content
  );

LOADER_ENTRY *
InternalAllocateLoaderEntry (
  VOID
  );

VOID
InternalFreeLoaderEntry (
  LOADER_ENTRY    *Entry
  );
  
EFI_STATUS
InternalProcessLoaderEntryFile (
  IN     CONST CHAR16           *FileName,
  IN OUT       CHAR8            *Content,
     OUT       LOADER_ENTRY     *Entry,
  IN     CONST BOOLEAN          Grub2
  );

/*
  GRUB variable.
*/
struct GRUB_VAR_ {
  //
  // Points within loaded file memory.
  //
  CHAR8               *Key;
  //
  // Points within loaded file memory, may be empty string.
  //
  CHAR8               *Value;
  //
  // GRUB var error code flags.
  //
  UINTN               Errors;
};

/*
  Loader entries.
*/

VOID
InternalFreePickerEntry (
  IN   OC_PICKER_ENTRY          *Entry
  );

EFI_STATUS
InternalConvertLoaderEntriesToBootEntries (
  IN   EFI_FILE_PROTOCOL        *RootDirectory,
  OUT  OC_PICKER_ENTRY          **Entries,
  OUT  UINTN                    *NumEntries
  );

EFI_STATUS
InternalScanLoaderEntries (
  IN   EFI_FILE_PROTOCOL        *RootDirectory,
  OUT  OC_PICKER_ENTRY          **Entries,
  OUT  UINTN                    *NumEntries
  );

EFI_STATUS
InternalIdVersionFromFileName (
  IN OUT LOADER_ENTRY  *Entry,
  IN     CHAR16        *FileName
  );

/*
  BLSpec / blscfg loader entry.
  Some items within here probably don't need to be allocated and could stay
  pointing within the source file as long as that is in memory (specifically
  Version and Linux, which are probably never going to be modified), but to
  keep things sane everything is (re)allocated.
*/
struct LOADER_ENTRY_ {
  //
  // Version.
  // Must come first since used for sorting.
  //
  CHAR8                 *Version;
  //
  // Title.
  //
  CHAR8                 *Title;
  //
  // Linux kernel filename.
  //
  CHAR8                 *Linux;
  //
  // Options.
  //
  OC_FLEX_ARRAY      *Options;
  //
  // Initrds.
  //
  OC_FLEX_ARRAY      *Initrds;
  //
  // OpenCore entry id.
  // 'id' line is not read from .conf files even if present.
  //
  CHAR8                 *OcId;
  //
  // Flavour.
  //
  CHAR8                 *OcFlavour;
  //
  // Is this an auxiliary entry for OpenCore?
  //
  BOOLEAN               OcAuxiliary;
  //
  // Has this already been scanned whilst assigned main and auxiliary entries?
  //
  BOOLEAN               DuplicateIdScanned;
};

struct VMLINUZ_FILE_ {
  CHAR16                *FileName;
  CHAR16                *Version;
  UINTN                 StrLen;
};

/*
  Autodetect.
*/
EFI_STATUS
AutodetectLinux (
  IN   EFI_FILE_PROTOCOL        *RootDirectory,
  OUT  OC_PICKER_ENTRY          **Entries,
  OUT  UINTN                    *NumEntries
  );

/*
  Insert root=PARTUUID=... option.
*/
EFI_STATUS
InsertRootOption (
  IN           OC_FLEX_ARRAY      *Options
  );

/*
  Sorts versions low to high.
*/
INTN
EFIAPI
InternalVersionCompare (
  IN CONST VOID           *Version1,
  IN CONST VOID           *Version2
 );

/*
  Sorts versions high to low.
*/
INTN
EFIAPI
InternalReverseVersionCompare (
  IN CONST VOID           *Version1,
  IN CONST VOID           *Version2
 );

#endif // LINUX_BOOT_INTERNAL_H
